package com.sc.nft.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sc.nft.dao.DaoWalletDao;
import com.sc.nft.dao.DaoWalletDetailDao;
import com.sc.nft.dao.TradingPoolAccountDao;
import com.sc.nft.dao.TradingPoolAccountLogDao;
import com.sc.nft.entity.DaoWallet;
import com.sc.nft.entity.DaoWalletDetail;
import com.sc.nft.entity.TradingPoolAccount;
import com.sc.nft.entity.TradingPoolAccountLog;
import com.sc.nft.enums.OperateDaoWalletAccountTypeEnum;
import com.sc.nft.enums.RedisKeyEnum;
import com.sc.nft.enums.TradingPoolAccountCategoryEnum;
import com.sc.nft.enums.TradingPoolAccountTypeEnum;
import com.sc.nft.enums.wallet.DaoWalletTypeEnum;
import com.sc.nft.exception.GlobalRunTimeException;
import com.sc.nft.service.DaoWalletService;
import com.sc.nft.sup.ErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * dao钱包表服务接口实现
 *
 * @author zxy
 * @description 由 Mybatisplus Code Generator 创建
 * @since 2023-12-16 19:17:50
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class DaoWalletServiceImpl implements DaoWalletService {
    private final DaoWalletDao daoWalletDao;
    private final DaoWalletDetailDao daoWalletDetailDao;
    private final TradingPoolAccountDao tradingPoolAccountDao;
    private final TradingPoolAccountLogDao tradingPoolAccountLogDao;
    private final Redisson redisson;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addBalance(Long daoId, Long sourceId, BigDecimal amount, BigDecimal charge, DaoWalletTypeEnum type, String remark) {
        DaoWallet wallet = daoWalletDao.getByDaoId(daoId);
        DaoWalletDetail detail = new DaoWalletDetail();
        detail.setDaoId(wallet.getDaoId());
        detail.setAmount(amount);
        detail.setCharge(charge);
        detail.setSourceId(sourceId);
        detail.setRealAmount(amount.subtract(charge));
        detail.setBeforeAmount(wallet.getBalance());
        detail.setIsAdd(true);
        detail.setAfterAmount(wallet.getBalance().add(detail.getRealAmount()));
        detail.setType(type);
        detail.setRemark(remark);
        detail.insert();
        daoWalletDao.addBalance(daoId, detail.getRealAmount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addBalance(Long daoId, Long sourceId, BigDecimal amount, BigDecimal charge, DaoWalletTypeEnum type, String remark, OperateDaoWalletAccountTypeEnum accountType) {
        if (Objects.equals(accountType, OperateDaoWalletAccountTypeEnum.BALANCE_ACCOUNT)) {
            this.addBalance(daoId, sourceId, amount, charge, type, remark);
        } else if (Objects.equals(accountType, OperateDaoWalletAccountTypeEnum.GIFT_INCOME_ACCOUNT)) {
            String key = RedisKeyEnum.TRADING_POOL_ADD.getKey() + daoId;
            RLock lock = redisson.getLock(key);
            try {
                //尝试数据加锁
                if (!lock.tryLock(30, TimeUnit.SECONDS)) {
                    throw new GlobalRunTimeException("操作频繁，请稍后重试");
                }
                List<TradingPoolAccount> accounts = tradingPoolAccountDao.listByDaoId(daoId);
                TradingPoolAccount tradingPoolAccount;
                if (CollectionUtils.isNotEmpty(accounts)) {
                    Assert.isTrue(accounts.size() == 1, () -> new GlobalRunTimeException("礼包收入账户异常"));
                    tradingPoolAccount = accounts.get(0);
                    Assert.isTrue(tradingPoolAccountDao.addGiftIncomeAccount(tradingPoolAccount.getId(), amount), () -> new GlobalRunTimeException("添加礼包收入账户金额失败"));
                } else {
                    tradingPoolAccount = new TradingPoolAccount();
                    tradingPoolAccount.setFrozenAccount(BigDecimal.ZERO);
                    tradingPoolAccount.setGiftIncomeAccount(amount);
                    tradingPoolAccount.setDaoId(daoId);
                    tradingPoolAccountDao.save(tradingPoolAccount);
                }
                //region 账户变化
                TradingPoolAccountLog tradingPoolAccountLog = new TradingPoolAccountLog();
                tradingPoolAccountLog.setDaoId(daoId);
                tradingPoolAccountLog.setOperationTime(DateTime.now());
                tradingPoolAccountLog.setQuantity(amount);
                tradingPoolAccountLog.setBeforeChange(tradingPoolAccount.getGiftIncomeAccount());
                tradingPoolAccountLog.setAfterChange(tradingPoolAccount.getGiftIncomeAccount().add(amount));
                tradingPoolAccountLog.setCategory(TradingPoolAccountCategoryEnum.GIFT_INCOME_ACCOUNT);
                tradingPoolAccountLog.setIsAdd(Boolean.TRUE);
                tradingPoolAccountLog.setSource(TradingPoolAccountTypeEnum.SYSTEM);
                tradingPoolAccountLog.setBeneficiary(TradingPoolAccountTypeEnum.GIFT_INCOME_ACCOUNT);
                tradingPoolAccountLog.setRemark(remark);
                if (!tradingPoolAccountLogDao.save(tradingPoolAccountLog)) {
                    throw new GlobalRunTimeException("礼包账户日志新增异常");
                }
            } catch (Exception e) {
                log.error("操作企业钱包余额异常", e);
                throw new GlobalRunTimeException(e.getMessage());
            } finally {
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        } else {
            throw new GlobalRunTimeException("不支持此种账号类型");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void subBalance(Long daoId, Long sourceId, BigDecimal amount, BigDecimal charge, DaoWalletTypeEnum type, String remark) {
        DaoWallet wallet = daoWalletDao.getByDaoId(daoId);
        DaoWalletDetail detail = new DaoWalletDetail();
        detail.setDaoId(wallet.getDaoId());
        detail.setAmount(amount);
        detail.setCharge(charge);
        detail.setSourceId(sourceId);
        detail.setRealAmount(amount.subtract(charge));
        detail.setBeforeAmount(wallet.getBalance());
        detail.setIsAdd(false);
        detail.setAfterAmount(wallet.getBalance().subtract(detail.getRealAmount()));
        detail.setType(type);
        detail.setRemark(remark);
        detail.insert();
        daoWalletDao.subBalance(daoId, detail.getRealAmount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void subBalance(Long daoId, Long sourceId, BigDecimal amount, BigDecimal charge, DaoWalletTypeEnum type, String remark, OperateDaoWalletAccountTypeEnum accountType) {
        if (Objects.equals(accountType, OperateDaoWalletAccountTypeEnum.BALANCE_ACCOUNT)) {
            this.subBalance(daoId, sourceId, amount, charge, type, remark);
        } else if (Objects.equals(accountType, OperateDaoWalletAccountTypeEnum.GIFT_INCOME_ACCOUNT)) {
            String key = RedisKeyEnum.TRADING_POOL_STOCK.getKey() + daoId;
            RLock lock = redisson.getLock(key);
            try {
                //尝试数据加锁
                if (!lock.tryLock(30, TimeUnit.SECONDS)) {
                    throw new GlobalRunTimeException("操作频繁，请稍后重试");
                }
                List<TradingPoolAccount> tradingPoolAccountList = tradingPoolAccountDao.listByDaoId(daoId);
                Assert.isTrue(CollectionUtil.isNotEmpty(tradingPoolAccountList), () -> new GlobalRunTimeException("礼包收入账户金额不足"));
                Assert.isTrue(tradingPoolAccountList.size() == 1, () -> new GlobalRunTimeException("礼包收入账户异常"));
                TradingPoolAccount tradingPoolAccount = tradingPoolAccountList.get(0);
                Assert.isTrue(tradingPoolAccount.getGiftIncomeAccount().compareTo(amount) >= 0, () -> new GlobalRunTimeException("礼包收入账户金额不足"));
                Assert.isTrue(tradingPoolAccountDao.reduceGiftIncomeAccount(tradingPoolAccount.getId(), amount), () -> new GlobalRunTimeException("礼包收入账户金额不足"));
                //region 账户变化
                TradingPoolAccountLog tradingPoolAccountLog = new TradingPoolAccountLog();
                tradingPoolAccountLog.setDaoId(daoId);
                tradingPoolAccountLog.setOperationTime(DateTime.now());
                tradingPoolAccountLog.setQuantity(amount);
                tradingPoolAccountLog.setBeforeChange(tradingPoolAccount.getGiftIncomeAccount());
                tradingPoolAccountLog.setAfterChange(tradingPoolAccount.getGiftIncomeAccount().subtract(amount));
                tradingPoolAccountLog.setCategory(TradingPoolAccountCategoryEnum.GIFT_INCOME_ACCOUNT);
                tradingPoolAccountLog.setIsAdd(Boolean.FALSE);
                tradingPoolAccountLog.setSource(TradingPoolAccountTypeEnum.GIFT_INCOME_ACCOUNT);
                tradingPoolAccountLog.setBeneficiary(TradingPoolAccountTypeEnum.SYSTEM);
                tradingPoolAccountLog.setRemark(remark);
                if (!tradingPoolAccountLogDao.save(tradingPoolAccountLog)) {
                    throw new GlobalRunTimeException(ErrorCode.OPERATE_FAIL);
                }
                //endregion
            } catch (Exception e) {
                log.error("操作企业钱包余额异常", e);
                throw new GlobalRunTimeException(e.getMessage());
            } finally {
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        } else {
            throw new GlobalRunTimeException("不支持此种账号类型");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refuse(Long daoId, Long sourceId, BigDecimal amount) {
        DaoWallet wallet = daoWalletDao.getByDaoId(daoId);
        DaoWalletDetail detail = new DaoWalletDetail();
        detail.setDaoId(wallet.getDaoId());
        detail.setSourceId(sourceId);
        detail.setAmount(amount);
        detail.setCharge(BigDecimal.ZERO);
        detail.setSourceId(null);
        detail.setRealAmount(amount);
        detail.setBeforeAmount(wallet.getBalance());
        detail.setIsAdd(true);
        detail.setAfterAmount(wallet.getBalance().add(detail.getRealAmount()));
        detail.setType(DaoWalletTypeEnum.TRANSFER_REFUND);
        detail.setRemark(detail.getType().getDescription());
        detail.insert();
        daoWalletDao.refuse(daoId, detail.getRealAmount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void freezeBalance(Long daoId, Long sourceId, BigDecimal amount, BigDecimal charge, DaoWalletTypeEnum
            type, String remark) {
        DaoWallet wallet = daoWalletDao.getByDaoId(daoId);
        Assert.isTrue(NumberUtil.isGreaterOrEqual(wallet.getBalance(), amount), () -> new GlobalRunTimeException("可用余额不足"));
        DaoWalletDetail detail = new DaoWalletDetail();
        detail.setDaoId(wallet.getDaoId());
        detail.setAmount(amount);
        detail.setCharge(charge);
        detail.setSourceId(sourceId);
        detail.setRealAmount(amount.subtract(charge));
        detail.setBeforeAmount(wallet.getBalance());
        detail.setIsAdd(false);
        detail.setAfterAmount(wallet.getBalance().subtract(detail.getRealAmount()));
        detail.setType(type);
        detail.setRemark(remark);
        detail.insert();
        daoWalletDao.approval(daoId, detail.getRealAmount());
    }

    @Override
    public DaoWallet getByDaoId(Long daoId) {
        return daoWalletDao.getByDaoId(daoId);
    }

    @Override
    public Page<DaoWalletDetail> getDaoWalletDetailPage(Long daoId, DaoWalletTypeEnum type, Date startTime, Date
            endTime, Page page) {
        return daoWalletDetailDao.getDaoWalletDetailPage(daoId, type, startTime, endTime, page);
    }
}